/* * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.http.converter.json; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.lang.reflect.Type; import java.nio.charset.Charset; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.util.Assert; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonIOException; import com.google.gson.JsonParseException; import com.google.gson.JsonSyntaxException; /** * CHRISTIAN KLOCK, 2014.09.16 * Class is left as obtained from * http://grepcode.com/file_/repo1.maven.org/maven2/org.springframework.android/spring-android-rest-template/1.0.1.RELEASE/org/springframework/http/converter/json/GsonHttpMessageConverter.java/?v=source * * * ERKO HANSAR, 2015.04.29 * Renamed the file from GsonHttpMessageConverter to GsonHttpMessageConverterForSpring3 to avoid name clashing with Spring 4, which now includes a GsonHttpMessageConverter. * TODO: When TWE upgrades to Spring 4, start using the Spring included version. * * * @author Roy Clarkson * @since 1.0 */ public class GsonHttpMessageConverterForSpring3 extends AbstractHttpMessageConverter<Object>{ public static final Charset DEFAULT_CHARSET = Charset.forName( "UTF-8" ); private Gson gson; private Type type = null; private boolean prefixJson = false; /** * Construct a new {@code GsonHttpMessageConverter} with a default {@link Gson#Gson() Gson}. */ public GsonHttpMessageConverterForSpring3(){ this( new Gson() ); } /** * Construct a new {@code GsonHttpMessageConverter}. * * @param serializeNulls true to generate json for null values */ public GsonHttpMessageConverterForSpring3( boolean serializeNulls ){ this( serializeNulls ? new GsonBuilder().serializeNulls().create() : new Gson() ); } /** * Construct a new {@code GsonHttpMessageConverter}. * * @param gson a customized {@link Gson#Gson() Gson} */ public GsonHttpMessageConverterForSpring3( Gson gson ){ super( new MediaType( "application", "json", DEFAULT_CHARSET ) ); setGson( gson ); } /** * Sets the {@code Gson} for this view. If not set, a default * {@link Gson#Gson() Gson} is used. * <p>Setting a custom-configured {@code Gson} is one way to take further control of the JSON serialization * process. * @throws IllegalArgumentException if gson is null */ public void setGson( Gson gson ){ Assert.notNull( gson, "'gson' must not be null" ); this.gson = gson; } public void setType( Type type ){ this.type = type; } public Type getType(){ return type; } /** * Indicates whether the JSON output by this view should be prefixed with "{} &&". Default is false. * <p> Prefixing the JSON string in this manner is used to help prevent JSON Hijacking. The prefix renders the string * syntactically invalid as a script so that it cannot be hijacked. This prefix does not affect the evaluation of JSON, * but if JSON validation is performed on the string, the prefix would need to be ignored. */ public void setPrefixJson( boolean prefixJson ){ this.prefixJson = prefixJson; } @Override public boolean canRead( Class<?> clazz, MediaType mediaType ){ return canRead( mediaType ); } @Override public boolean canWrite( Class<?> clazz, MediaType mediaType ){ return canWrite( mediaType ); } @Override protected boolean supports( Class<?> clazz ){ // should not be called, since we override canRead/Write instead throw new UnsupportedOperationException(); } @Override protected Object readInternal( Class<?> clazz, HttpInputMessage inputMessage ) throws IOException, HttpMessageNotReadableException{ Reader json = new InputStreamReader( inputMessage.getBody(), getCharset( inputMessage.getHeaders() ) ); try{ Type typeOfT = getType(); if( typeOfT != null ){ return this.gson.fromJson( json, typeOfT ); } else{ return this.gson.fromJson( json, clazz ); } } catch( JsonSyntaxException ex ){ throw new HttpMessageNotReadableException( "Could not read JSON: " + ex.getMessage(), ex ); } catch( JsonIOException ex ){ throw new HttpMessageNotReadableException( "Could not read JSON: " + ex.getMessage(), ex ); } catch( JsonParseException ex ){ throw new HttpMessageNotReadableException( "Could not read JSON: " + ex.getMessage(), ex ); } } @Override protected void writeInternal( Object o, HttpOutputMessage outputMessage ) throws IOException, HttpMessageNotWritableException{ OutputStreamWriter writer = new OutputStreamWriter( outputMessage.getBody(), getCharset( outputMessage.getHeaders() ) ); try{ if( this.prefixJson ){ writer.append( "{} && " ); } Type typeOfSrc = getType(); if( typeOfSrc != null ){ this.gson.toJson( o, typeOfSrc, writer ); } else{ this.gson.toJson( o, writer ); } writer.close(); } catch( JsonIOException ex ){ throw new HttpMessageNotWritableException( "Could not write JSON: " + ex.getMessage(), ex ); } } // helpers private Charset getCharset( HttpHeaders headers ){ if( headers != null && headers.getContentType() != null && headers.getContentType().getCharSet() != null ){ return headers.getContentType().getCharSet(); } return DEFAULT_CHARSET; } }